今天來講講這幾天陸續有提到的 BPP。BPP 的全名叫做 BeanPostProcessor,中文直翻的話,可以把它稱作 Bean 的後置處理器。最原始的設計用途是「處理 Bean 在初始化前後的相關操作。」介面也只有 2 個單純的方法。但因為框架的演進,後續有些 BPP 的子介面開始承擔初始化外的其他事項,使 BPP 逐漸變成一個大統包的巨型介面,算是 Spring 框架在架構原則、設計模式,和易讀性與可維護權性衡下的一種妥協。

初始化邏輯
就像剛剛說的:原始的 BPP 只有 before 方法(post-Process-Before-Initialization)和 after 方法(post-Process-After-Initialization)兩個定義。這兩個方法會在 doCreateBean 的步驟 7-9 之間被調用。前者用在初始化之前,對剛賦值完的 Bean 做一些修改、操作、或方法的調用,比較具有代表性的例子是「掃描並執行 Bean 物件中帶有 @PostConstruct 註解的初始化方法。」
後者則是處理 Bean 物件初始化之後的相關邏輯。這裡因為是整個創建週期中,能夠介入、調整、甚至是替換 Bean 的最後機會,因此這裡也是黑科技最多的一個地方。after 方法最有名的應用是 AOP 動態代理 —— 透過 Spring AOP 的相關組件,Spring 可以把原始的 Bean 替換成一個帶有 AOP 切面邏輯的代理物件。並將這個代理物件回傳給 IoC 容器,使其能被容器視為一個 Bean,以此進行後續的調用與管理。

這兩個方法依序會在步驟 7 和步驟 9 被調用,那既然都說到這裡的,這邊也來聊聊創建方法的步驟 8:invoke-Init-Methods 方法內部預設有 2 個執行邏輯:如果當前創建中的 Bean 有實作 InitializingBean 介面,這裡就會調用介面內的 after-Properties-Set 方法,對當前的 Bean 進行初始化;此外,如果創建中的 Bean 有設定 init-Method-Name(通常透過 @Bean 註解的 initMethod 參數設定),系統也會在這裡執行 Bean 自定義的初始化方法。
但因為在日常的開發下,我們不太有讓 Bean 實作 InitializingBean 介面的動機、也不會對大部分的 Bean 指定一個 init Method,因此創建方法的步驟 8 對於大多數的 Bean 來講,都是一個會直接跳過的空階段。這裡少數有個比較大眾的例子是:Spring MVC 的 Handler-Mapping 組件會實作 InitializingBean 介面,被調用時會遍歷 IoC 容器的所有 @Controller,建立起 @GetMapping, @PostMapping 的 API 映射表。
初始化之外
除了初始化前後的介入時機以外,就像開頭說的,BPP 還可以對其他的生命週期(實例化 & 賦值)進行介入與調整:負責這兩個生命週期的 BPP 的子介面叫 Instantiation-Aware-Bean-Post-Processor(簡稱 IABPP)。該介面在原本的基礎下,擴充了 3 個額外的方法定義,分別是 post-Process-Before-Instantiation(簡稱 before-Instantiation)、post-Process-After-Instantiation(簡稱 after-Instantiation)、和 post-Process-Properties(簡稱 properties 方法)。

before-Instantiation 用來在實例化物件前,對物件進行介入操作。這邊的一個小細節是:在預設的流程上,如果一個 Bean 的 before-instant 方法返回的不是 null。Spring 就會跳過這個 Bean 後續的賦值和初始化,直接進行最後的初始化後邏輯(也就是 after 方法的調用)。並將最後的處理回傳作為創建 Bean 的最終結果。在 @MockBean, @SpyBean ...等測試框架上,有時候可以看到它們的出現。
after-Instantiation 方法在實際的應用上很少出現。它本身是一個 boolean 的判斷方法,主要的用途是「決定這個 Bean 後續要不要執行『賦值』操作。」如果回傳 true(預設就是 true),後面就會繼續普通的賦值、初始化 ...等正常的創建週期;但如果 after-Instantiation 回傳 false,系統就會跳過後續的賦值階段,直接進入到初始化前、初始化、和初始化後的相關處理。
properties 方法是 IABPP 介面裡最常用到的方法沒有之一,主要的用途是接手賦值。具體來說,當一個 Bean 走到創建週期走到賦值階段(也就是 doCreateBean 裡面的步驟 4-6),IoC 容器就會依序遍歷所有已註冊的 IABPP,並嘗試呼叫這些 IABPP 組件的 properties 方法,為當前的 Bean 物件進行屬性賦值與依賴注入的相關工作。
在這之中,Autowired-Annotation-Bean-Post-Processor(簡稱 AABPP)是所有 IABPP 實例裡最可以提的一個。它主要負責對 Bean 物件中的 @Autowired 和 @Value 這兩個註解進行解析與賦值。簡單來講,在 Spring 裡面主要負責做「依賴注入」這個工作的組件,就是 AABPP。至於 @Resource 註解,背後則是使用 Common-Annotation-Bean-Post-Processor 這個 IABPP 實例負責的。
最後,以宏觀一點的角度來看,我們可以發現 BPP 的處理工作其實非常「微觀」。準確一點的說,BPP 只關心一個 BD 在創建週期的流水線上怎麼實例化、怎麼賦值、怎麼初始化、和這些步驟前後是否有什麼介入的邏輯 ...等。它並不關心這條流水線是怎麼架設的?有哪些 BD 會走進這個流水線?又該怎麼對進入流水線的「BD 本身」進行介入與修改 ...等。明天,我們就來談談另一個跟容易跟 BPP 搞混的 BFPP 介面,看看 Spring 框架是怎麼處理這條流水線的。
期待後續各位的閱讀與分享,我是 Pax,我們明天見。